/**
 * 
 */
package parallel.project.framework;

import java.awt.Point;
import java.awt.Rectangle;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import parallel.project.graph.Path;

/**
 * @author aclayton
 *
 */
public class Mazemap implements Serializable{
	private static final long serialVersionUID = 7526472295622776147L;
	private int width, height;
	private Point start, end;
	private int[][] maze;
	private int startingColumn;
	
	public Mazemap(){
		this.width = 0;
		this.height = 0;
		start = null;
		end = null;
		maze = new int[width][height];
		for(int a = 0; a < width; a++){
			for(int b = 0; b < height; b++)
				maze[a][b] = 1;
		}
		this.startingColumn = 0;
	}
	
	public Mazemap(int width, int height){
		this.width = width;
		this.height = height;
		maze = new int[width][height];
		for(int a = 0; a < width; a++){
			for(int b = 0; b < height; b++)
				maze[a][b] = 1;
		}
		this.startingColumn = 0;
	}
	
	public int getCell(int x, int y){
		return maze[x][y];
	}
	
	public void setCell(int x, int y, int val){
		maze[x][y] = val;
	}
	
	public boolean isPassage(int x, int y){
		return maze[x][y] == 0;
	}
	
	public ArrayList<Point> getPassagePoints(){
		ArrayList<Point> p = new ArrayList<Point>();
		for(int a = 0; a < width; a++){
			for(int b = 0; b < height; b++){
				if(maze[a][b] == 0)
					p.add(new Point(a,b));
			}
		}
		
		return p;
	}
	
	public ArrayList<Point> getWallPoints(){
		ArrayList<Point> p = new ArrayList<Point>();
		for(int a = 0; a < width; a++){
			for(int b = 0; b < height; b++){
				if(maze[a][b] == 1)
					p.add(new Point(a,b));
			}
		}
		
		return p;
	}
	
	/**
	 * @return the width
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * @param width the width to set
	 */
	public void setWidth(int width) {
		this.width = width;
	}

	/**
	 * @return the height
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * @param height the height to set
	 */
	public void setHeight(int height) {
		this.height = height;
	}

	/**
	 * @return the maze
	 */
	public int[][] getMaze() {
		return maze;
	}

	/**
	 * @param maze the maze to set
	 */
	public void setMaze(int[][] maze) {
		this.maze = maze;
	}

	/**
	 * @return the start
	 */
	public Point getStart() {
		return start;
	}

	/**
	 * @param start the start to set
	 */
	public void setStart(Point start) {
		this.start = start;
	}

	/**
	 * @return the end
	 */
	public Point getEnd() {
		return end;
	}

	/**
	 * @param end the end to set
	 */
	public void setEnd(Point end) {
		this.end = end;
	}
	
	public Rectangle getBounds(){
		return new Rectangle(width, height);
	}

	/**
	 * @return the global starting column of this (sub)maze's 0th
	 * 		column
	 */
	public int getStartingColumn() {
		return startingColumn;
	}

	/**
	 * @param startingColumn 
	 * 		the global starting column of this maze's 0th column
	 */
	public void setStartingColumn(int startingColumn) {
		this.startingColumn = startingColumn;
	}
	
	public void printMazemap() {
		this.printMazemap(null);
	}
	
	public void printMazemap(Path path){
		System.out.println("Width = " + width);
		System.out.println("Height = " + height);
		int a, b;
		for(a = 0; a < height; a++){
			for(b = 0; b < width; b++){
				Point loc = new Point(b,a);
				if (this.getStart() != null &&
					this.getStart().equals(loc))
					System.out.print("S, ");
				else if (this.getEnd() != null && 
						 this.getEnd().equals(loc))
					System.out.print("E, ");
				else if (path != null && path.containsPoint(loc))
					System.out.print("X, ");
				else
					System.out.print(maze[b][a] + ", ");
			}
			System.out.print("\n");
		}
		
	}
	
	/**
	 * Splits the given maze into the given number of sub-mazes, each
	 * of equal size.  If it cannot be split up into the given number
	 * of partitions where each is equal size, then null is returned.
	 * Otherwise, all the sub-mazes are returned, and 2D arrays.
	 * 
	 * @param maze
	 * 		The maze to partition
	 * @param partitions
	 * 		The number of parts to split the maze up into; must be less
	 * 		than or equal to the number of columns in the maze
	 * 
	 * @return
	 * 		All the partitions of the maze, partitioned from left to
	 * 		right
	 */
	public List<Mazemap> splitMaze(int partitions)
		throws MazeException
	{
		// check that can split by columns
		if (partitions > getWidth())
		{
			throw new MazeException("Can't split maze into " + 
				partitions + " cause there are not that many columns");
		}
		int width = getWidth();
		int height = getHeight();
		// list of the maze splits
		List<Mazemap> splits = new ArrayList<Mazemap>();

		// need to indicate the starting column in the full maze for
		// every split maze, since every split maze is its own 
		// coordinate space need to know the starting column to get 
		// back to global space
		
		// split the maze by columns, find the number of columns in
		// each partition, integer division so rounding down
		int columnsPerPart = width / partitions;
		// current column
		int currentColumn = 0;
		// go through the maze columns, extracting a segment of the
		// column size per partition just calculated; there is the
		// possibility that one of the partitions will be a larger or
		// smaller size than the rest, but oh well, it can be left up
		// to the user to pick a meaningful number of partitions and
		// maze size
		int[][] mazeGrid = getMaze();
		for (int i = 0; i < partitions; i++)
		{
			// width of this submaze
			int subWidth;
			int columnsRemaining = width - currentColumn;
			// use the calculated submaze width unless this is the
			// last partition, in which case just have to use the 
			// number of columns remaining
			if (i == (partitions - 1))
				subWidth = columnsRemaining;
			else
				subWidth = columnsPerPart;
//			// if there are more columns than the calculated number of
//			// columns per partition, then use the calculated submaze
//			// width
//			if (columnsRemaining >= columnsPerPart)
//				subWidth = columnsPerPart;
//			// otherwise, not enough columns, so just use whichever ones
//			// are left
//			else
//				subWidth = columnsRemaining;
			// create the space for the submaze
			int[][] submaze = new int[subWidth][getHeight()];
//			System.out.println("partition " + i + " [" + subWidth + 
//				"," + getHeight() + "]");
			// copy over the values
			for (int w = currentColumn; w < (currentColumn + subWidth);
				w++)
			{
				for (int h = 0; h < height; h++)
					submaze[w - currentColumn][h] = mazeGrid[w][h];
			}
			
			// configure the sub-maze
			Mazemap newSplit = 
				new Mazemap(submaze.length,submaze[0].length);
			newSplit.setMaze(submaze);
			// if this split contains the start and/or end positions,
			// then set them in the split maze
			Rectangle splitBounds = newSplit.getBounds();
			splitBounds.x = currentColumn;
			if (splitBounds.contains(getStart()))
			{
				// getLocation makes a copy of the point, so not
				// modifying the original start
				Point splitStart = getStart().getLocation();
				splitStart.x -= currentColumn;
				newSplit.setStart(splitStart);
			}
			if (splitBounds.contains(getEnd()))
			{
				// getLocation makes a copy of the point, so not
				// modifying the original end
				Point splitEnd = getEnd().getLocation();
				splitEnd.x -= currentColumn;
				newSplit.setEnd(splitEnd);
			}
			// find an open spot to start at
			// set the global starting column
			newSplit.setStartingColumn(currentColumn);
			// add the sub-maze to the list of sub-mazes
			splits.add(newSplit);
			
//			newSplit.printMazemap();
			
			// go to the next starting column
			currentColumn += columnsPerPart;
		}
		return splits;
	}
}
